package yredis

import (
	"context"
	"dgo/goutils/utils"
	"fmt"
	"gitee.com/ymofen/supervalue"
	"github.com/go-redis/redis/v8"
	"io"
	"strings"
	"sync/atomic"
	"time"
)

/*
*

使用说明：

	案列1：
	   拔掉网线5分钟, 3分钟左右会打印:redis: 2023/02/24 21:13:57 pubsub.go:159: redis: discarding bad PubSub connection: read tcp 192.168.20.90:52290->119.96.176.50:56379: wsarecv: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.
	   插上网线后30秒左右会重连收到数据

	案例2:
	   拔掉网线运行3分钟, 没有消息接收, 插上网线后，20秒开始收到数据

注意：

	  option: 改变后，需要重建clt
	  clt.close后, sub也会close

	  // sub 可以多次订阅成功
	  sub := clt.PSubscribe(ctx, "RAW-HEMI-HBF*")
	  time.AfterFunc(time.Second, func() {
		sub.PSubscribe(ctx, "RAW-HEMI-HBE*")
	  })
*/
type RedisInner struct {
	busyflag     int32
	clt          *redis.Client
	redisOpt     *redis.Options
	PoolSize     int
	LastErr      string
	hincrbyIfSha string
}

func NewRedisInner() *RedisInner {
	rval := &RedisInner{}
	return rval
}

// 支持匹配
func (this *RedisInner) NewPSubscribe(channels ...string) *redis.PubSub {
	ctx := context.Background()
	clt := this.clt
	if clt == nil {
		return nil
	}
	return clt.PSubscribe(ctx, channels...)
}

func (this *RedisInner) RedisAddr() string {
	if this.redisOpt == nil {
		return ""
	}
	return this.redisOpt.Addr
}

func (this *RedisInner) CheckInit() {
	clt := this.clt
	if clt == nil && this.redisOpt != nil && len(this.redisOpt.Addr) > 0 {
		this.CheckNewClt()
	}
	return
}

func (this *RedisInner) Ping() error {
	if this.clt == nil {
		return fmt.Errorf("尚未创建连接")
	}
	_, err := this.clt.Ping(context.Background()).Result()
	return err
}

func (this *RedisInner) Close() {
	if utils.GoFunCatchException {
		defer utils.PanicHandler()
	}
	this.RequestDis("关闭")
}

func (this *RedisInner) RequestDis(reason string) {
	clt := this.clt
	if clt != nil { // 关闭后相应的sub也会关闭
		clt.Close()
		this.clt = nil
	}
}

func (this *RedisInner) CheckNewClt() {
	if atomic.CompareAndSwapInt32(&this.busyflag, 0, 1) {
		this.RequestDis("配置发生改变")
		this.clt = redis.NewClient(this.redisOpt)
		atomic.StoreInt32(&this.busyflag, 0)
	} else {
		time.Sleep(time.Millisecond)
	}
}

func (this *RedisInner) CheckGetClt() *redis.Client {
	clt := this.clt
	if clt == nil && this.redisOpt != nil && len(this.redisOpt.Addr) > 0 {
		this.CheckNewClt()
	}
	return this.clt
}

func (this *RedisInner) SetKeyValue(key string, val string, exp time.Duration) error {
	clt := this.CheckGetClt()
	if clt == nil {
		this.LastErr = fmt.Sprintf("[%s]Set fail:服务器尚未进行配置", utils.NowString())
		return fmt.Errorf("服务器尚未进行配置")
	}
	_, err := clt.Set(ctx_background, key, val, exp).Result()
	if err != nil {
		this.LastErr = fmt.Sprintf("[%s]Set fail:%s", utils.NowString(), err.Error())
	}
	return err
}

func (this *RedisInner) GetKeys(pattern string) []string {
	clt := this.CheckGetClt()
	if clt == nil {
		this.LastErr = fmt.Sprintf("[%s]Get fail:服务器尚未进行配置", utils.NowString())
		return nil
	}
	rval, err := clt.Keys(ctx_background, pattern).Result()
	if err != nil {
		if err.Error() == "redis: nil" { // redis中没有值
			return rval
		}
		this.LastErr = fmt.Sprintf("[%s]Get fail:%s", utils.NowString(), err.Error())
		return nil
	}
	return rval
}

func (this *RedisInner) StringByName(key string, failresult string) string {
	clt := this.CheckGetClt()
	if clt == nil {
		this.LastErr = fmt.Sprintf("[%s]Get fail:服务器尚未进行配置", utils.NowString())
		return failresult
	}
	rval, err := clt.Get(ctx_background, key).Result()
	if err != nil {
		if err.Error() == "redis: nil" { // redis中没有值
			return rval
		}
		this.LastErr = fmt.Sprintf("[%s]Get fail:%s", utils.NowString(), err.Error())
		return failresult
	}
	return rval
}

func (this *RedisInner) HStringByName(key, field string) (string, error) {
	clt := this.clt
	if clt == nil {
		return "", io.ErrClosedPipe
	}
	rval, err := clt.HGet(ctx_background, key, field).Result()
	if err != nil {
		return "", err
	}
	return rval, nil
}

func (this *RedisInner) HIntByName(key, field string) (int, error) {
	clt := this.clt
	if clt == nil {
		return 0, io.ErrClosedPipe
	}
	rval, err := clt.HGet(ctx_background, key, field).Result()
	if err != nil {
		return 0, err
	}
	return utils.StrToIntDef(rval, 0), nil
}

func (this *RedisInner) SAdd(key string, members ...interface{}) (int64, error) {
	clt := this.clt
	if clt == nil {
		return 0, fmt.Errorf("尚未创建连接")
	}
	return clt.SAdd(ctx_background, key, members...).Result()
}

// 删除成功：1, 元素不存在返回:0
func (this *RedisInner) SDel(key string, members ...interface{}) (int64, error) {
	clt := this.clt
	if clt == nil {
		return 0, fmt.Errorf("尚未创建连接")
	}
	return clt.SRem(ctx_background, key, members...).Result()
}

func (this *RedisInner) SIsMember(key string, member interface{}) (bool, error) {
	clt := this.clt
	if clt == nil {
		return false, fmt.Errorf("尚未创建连接")
	}
	return clt.SIsMember(ctx_background, key, member).Result()
}

func (this *RedisInner) SLen(key string) (int64, error) {
	clt := this.clt
	if clt == nil {
		return 0, fmt.Errorf("尚未创建连接")
	}
	return clt.SCard(ctx_background, key).Result()
}

func (this *RedisInner) GetSetMemberList(key string) ([]string, error) {
	clt := this.clt
	if clt == nil {
		return nil, fmt.Errorf("尚未创建连接")
	}
	return clt.SMembers(ctx_background, key).Result()
}

// 移除并返回集合中的一个随机元素
func (this *RedisInner) SPop(key string) (string, error) {
	clt := this.clt
	if clt == nil {
		return "", fmt.Errorf("尚未创建连接")
	}
	return clt.SPop(ctx_background, key).Result()
}

// 成功：返回field的当前值
func (this *RedisInner) HIncrby(key, field string, delta int64) (int64, error) {
	clt := this.clt
	if clt == nil {
		return 0, fmt.Errorf("尚未创建连接")
	}
	return clt.HIncrBy(ctx_background, key, field, delta).Result()
}

func (this *RedisInner) HSet(key, field string, data string) (int64, error) {
	clt := this.clt
	if clt == nil {
		return 0, fmt.Errorf("尚未创建连接")
	}
	return clt.HSet(ctx_background, key, field, data).Result()
}

func (this *RedisInner) HDel(key, field string) (int64, error) {
	clt := this.clt
	if clt == nil {
		return 0, fmt.Errorf("尚未创建连接")
	}
	return clt.HDel(ctx_background, key, field).Result()
}

// redis 服务端是单线程操作, 所以可以视为安全的操作
func (this *RedisInner) HIncrbyIf(key string, field string, delta int64, max int64) (int, error) {
	clt := this.clt
	if clt == nil {
		return 0, fmt.Errorf("尚未创建连接")
	}
	scriptSha := this.hincrbyIfSha
	if len(scriptSha) == 0 {
		if atomic.CompareAndSwapInt32(&this.busyflag, 0, 1) {
			defer func() {
				atomic.StoreInt32(&this.busyflag, 0)
			}()
			scripttext := `local num = tonumber(redis.call('HGET', KEYS[1], ARGV[1]))
if num then
   if num + tonumber(ARGV[2]) > tonumber(ARGV[3]) then
	   return 0
   end
end    
redis.call('hincrby', KEYS[1], ARGV[1], ARGV[2])
return num + tonumber(ARGV[2])`
			val, err := clt.ScriptLoad(ctx_background, scripttext).Result()
			if err != nil {
				return 0, err
			}
			scriptSha = val
			this.hincrbyIfSha = scriptSha
		}
	} else {
		time.Sleep(time.Millisecond)
		scriptSha = this.hincrbyIfSha
	}
	if len(scriptSha) == 0 {
		return 0, fmt.Errorf("脚本尚未完成创建, 请稍后调用")
	}
	cmd := clt.EvalSha(ctx_background, scriptSha, []string{key}, field, delta, max)
	val, err := cmd.Result()
	if val == nil || (err != nil && strings.Contains(err.Error(), "NOSCRIPT")) {
		this.hincrbyIfSha = "" // 脚本不存在了, 下次调用需要重新创建;
		return 0, err
	}
	if v, ok := val.(int); ok {
		return v, nil
	}

	if v, ok := val.(int64); ok {
		return int(v), nil
	}

	return 0, fmt.Errorf("未知的数据格式%v", val)

}

func (this *RedisInner) Config(addr string, user, pass string) (isChanged bool) {
	if this.redisOpt == nil {
		this.redisOpt = &redis.Options{}
	}

	if this.redisOpt.Addr != addr {
		this.redisOpt.Addr = addr
		isChanged = true
	}

	if this.redisOpt.Username != user {
		this.redisOpt.Username = user
		isChanged = true
	}

	if this.redisOpt.Password != pass {
		this.redisOpt.Password = pass
		isChanged = true
	}
	return
}

func (this *RedisInner) ConnString() string {
	if this.redisOpt == nil {
		return ""
	}
	if len(this.redisOpt.Password) > 0 {
		return fmt.Sprintf("%s:****@%s", this.redisOpt.Username, this.redisOpt.Addr)
	}
	return fmt.Sprintf("%s@%s", this.redisOpt.Username, this.redisOpt.Addr)
}

func (this *RedisInner) StatusString() string {
	if this.redisOpt == nil {
		return "未配置"
	}
	if len(this.redisOpt.Addr) == 0 {
		return "连接信息尚未进行配置"
	}
	clt := this.clt
	if clt == nil {
		return "尚未创建连接到服务器"
	}
	if len(this.redisOpt.Password) > 0 {
		return fmt.Sprintf("%s:****@%s,%s", this.redisOpt.Username, this.redisOpt.Addr, this.LastErr)
	}
	return fmt.Sprintf("%s@%s,%s", this.redisOpt.Username, this.redisOpt.Addr, this.LastErr)
}

/*
*

		{
			"addr":"119.96.169.117:6379",
			"user":"",
			"pass":"",
		    "pool-size":10,
	        "readtimeout-ms":100, // 单位毫秒
		}
*/
func (this *RedisInner) ReloadConf(conf *wrapper.SuperValue) (isChanged bool) {
	if conf == nil {
		return false
	}

	if this.redisOpt == nil {
		this.redisOpt = &redis.Options{}
	}

	isChanged = false
	conf.CheckStrFunc("addr", func(strv string) {
		strv = utils.ReplacePlaceholder(strv, "$$", "$$")
		if this.redisOpt.Addr != strv {
			this.redisOpt.Addr = strv
			isChanged = true
		}
	})

	conf.CheckStrFunc("pool-size", func(strv string) {
		strv = utils.ReplacePlaceholder(strv, "$$", "$$")
		v := utils.StrToIntDef(strv, 5)
		if this.redisOpt.PoolSize != v {
			this.redisOpt.PoolSize = v
			isChanged = true
		}
	})

	conf.CheckExistsStrFunc("user", func(strv string) {
		strv = utils.ReplacePlaceholder(strv, "$$", "$$")
		if this.redisOpt.Username != strv {
			this.redisOpt.Username = strv
			isChanged = true
		}
	})

	conf.CheckExistsStrFunc("pass", func(strv string) {
		strv = utils.ReplacePlaceholder(strv, "$$", "$$")
		if this.redisOpt.Password != strv {
			this.redisOpt.Password = strv
			isChanged = true
		}
	})

	{
		strv := utils.ReplacePlaceholder(conf.StringByName("readtimeout-ms", "0"), "$$", "$$")
		ms := time.Duration(utils.StrToIntDef(strv, 0)) * time.Millisecond
		if ms != this.redisOpt.ReadTimeout {
			this.redisOpt.ReadTimeout = ms
			isChanged = true
		}
	}

	{
		strv := utils.ReplacePlaceholder(conf.StringByName("writetimeout-ms", "0"), "$$", "$$")
		ms := time.Duration(utils.StrToIntDef(strv, 0)) * time.Millisecond
		if ms != this.redisOpt.WriteTimeout {
			this.redisOpt.WriteTimeout = ms
			isChanged = true
		}
	}

	return
}

func (this *RedisInner) LIsMember(key string, member interface{}) (bool, error) {
	exists := false

	length, err := this.clt.LLen(context.Background(), key).Result()
	if err != nil {
		fmt.Println("Error LLEN:", err)
		return exists, err
	}

	// 使用 LRANGE 命令获取列表中的所有元素
	elements, err := this.clt.LRange(context.Background(), key, 0, length-1).Result()
	if err != nil {
		fmt.Println("Error LRANGE:", err)
		return exists, err
	}

	// 遍历列表中的所有元素，检查是否存在目标成员
	for _, e := range elements {
		if e == member {
			exists = true
			break
		}
	}
	return exists, nil
}

func (this *RedisInner) LLen(key string) (int64, error) {
	clt := this.clt
	if clt == nil {
		return 0, fmt.Errorf("尚未创建连接")
	}
	return clt.LLen(ctx_background, key).Result()
}
func (this *RedisInner) LPush(key string, data string) (int64, error) {
	clt := this.clt
	if clt == nil {
		return 0, fmt.Errorf("尚未创建连接")
	}
	return clt.LPush(ctx_background, key, data).Result()
}

func (this *RedisInner) RPush(key string, data string) (int64, error) {
	clt := this.clt
	if clt == nil {
		return 0, fmt.Errorf("尚未创建连接")
	}
	return clt.RPush(ctx_background, key, data).Result()
}

func (this *RedisInner) LPop(key string) (string, error) {
	clt := this.clt
	if clt == nil {
		return "0", fmt.Errorf("尚未创建连接")
	}
	return clt.LPop(context.Background(), key).Result()

}

func (this *RedisInner) RPop(key string) (string, error) {
	clt := this.clt
	if clt == nil {
		return "0", fmt.Errorf("尚未创建连接")
	}
	return clt.RPop(context.Background(), key).Result()

}

func (this *RedisInner) LRem(key string, value string) (int64, error) {
	clt := this.clt
	if clt == nil {
		return 0, fmt.Errorf("尚未创建连接")
	}
	return clt.LRem(context.Background(), key, 0, value).Result()

}
